
/**
 * Copyright 2009 cestum.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.waveprotocol.wave.model.document.operation.impl.impl {
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.DocInitializationCursor;
import org.waveprotocol.wave.model.document.operation.impl.DocumentOperationUtil;
import org.waveprotocol.wave.model.util.StringBuilder;
import org.goverla.collections.ArrayList;
import org.goverla.collections.HashMap;
import org.goverla.interfaces.IIterator;


public final class XMLDocInitializationCursor implements
		DocInitializationCursor {

	var b:StringBuilder;
    public function XMLDocInitializationCursor(b:StringBuilder) {
		this.b = b;
	}

    var currentAnnotations:HashMap= new HashMap();
    var changes:HashMap= new HashMap();
    var tags:ArrayList= new ArrayList();
    var elementPart:String;
    
    public function annotationBoundary(map:AnnotationBoundaryMap):void {
      changes.clear();
      for (var i:int= 0; i < map.changeSize(); i++) {
        var key:String= map.getChangeKey(i);
        var value:String= map.getNewValue(i);
        if (!equal(String(currentAnnotations.getItem(key)), value)) {
          // removal not necessary if null, get will return the same in either case.
          currentAnnotations.addItem(key, value);
          changes.addItem(key, value);
        }
      }
      for (var i:int= 0; i < map.endSize(); i++) {
        var key:String= map.getEndKey(i);
        if (currentAnnotations.getItem(key) != null) {
          currentAnnotations.removeItemAt(key);
          changes.addItem(key, null);
        }
      }

      if (changes.keys != null && changes.keys.length == 0) {
        return;
      }

      if (elementPart != null) {
        b.append(elementPart + ">");
        elementPart = null;
      }
      b.append("<?" + DocumentOperationUtil.PI_TARGET);
	  var itr:IIterator = changes.keyIterator();
	  while(itr.hasNext()) {
		  var key:String = itr.next() as String;
		  if (key != null) {
			  b.append(" " +  key + "=\"" + DocumentOperationUtil.annotationEscape(String(changes.getItem(key))) + "\"");
		  } else {
			  // This code renders ending annotations and annotations that are
			  // changed to null the same way, which is OK since we are
			  // only concerned with DocIntializations.  (It's, in fact, the
			  // only correct solution since our test cases use this code for
			  // equality comparison of documents.)
			  b.append(" " + key);
		  }
	  }
      b.append("?>");
    }

    
    public function characters(chars:String):void {
      if (elementPart != null) {
        b.append(elementPart + ">");
        elementPart = null;
      }
      b.append(DocumentOperationUtil.xmlTextEscape(chars));
    }

    
    public function elementStart(type:String, attrs:Attributes):void {
      if (elementPart != null) {
        b.append(elementPart + ">");
        elementPart = null;
      }
      elementPart = "<" + type + (attrs.isEmpty() ? "" : " " + DocumentOperationUtil.attributeString(attrs));
      tags.addItem(type);
    }

    
    public function elementEnd():void {
      if (elementPart != null) {
        b.append(elementPart + "/>");
        elementPart = null;
        org.waveprotocol.wave.model.util.Assert.assert(tags.length> 0);
        tags.removeItem(tags.length- 1);
      } else {
        var tag:String;
        tag = String(tags.removeItem(tags.length- 1));
        b.append("</" + tag + ">");
      }
    }

    private function equal(a:String, b:String):Boolean {
      return a == null ? b == null : a == (b);
    }

  

}
}